	
#include "global.h"
#include "RageDisplay.h"
#include "RageDisplay_GX.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "RageTimer.h"
#include "RageException.h"
#include "RageTexture.h"
#include "RageTextureManager.h"
#include "RageMath.h"
#include "RageTypes.h"
#include "RageFile.h"
#include "GameConstantsAndTypes.h"
#include "StepMania.h"
#include "RageUtil.h"
#include "PrefsManager.h"
#include "RageSurface.h"
#include "RageSurfaceUtils.h"

#include "arch/arch.h"

#include <malloc.h>
#include <gccore.h>
#include <ogc/gx.h>
#include <ogc/gu.h>
#include <Metaphrasis.h>
#include <sys/iosupport.h>

#define DEFAULT_FIFO_SIZE (256*1024)

GXRModeObj *vmode;
const devoptab_t *origOUT, *origERR;

static RageDisplay::PixelFormatDesc PIXEL_FORMAT_DESC[RageDisplay::NUM_PIX_FORMATS] = {
	{
		/* R8G8B8A8 */
		32,
		{ 0xFF000000,
		  0x00FF0000,
		  0x0000FF00,
		  0x000000FF }
	}, {
		/* R4G4B4A4 */
		16,
		{ 0xF000,
		  0x0F00,
		  0x00F0,
		  0x000F },
	}, {
		/* R5G5B5A1 */
		16,
		{ 0xF800,
		  0x07C0,
		  0x003E,
		  0x0001 },
	}, {
		/* R5G5B5 */
		16,
		{ 0xF800,
		  0x07C0,
		  0x003E,
		  0x0000 },
	}, {
		/* R8G8B8 */
		24,
		{ 0xFF0000,
		  0x00FF00,
		  0x0000FF,
		  0x000000 }
	}, {
		/* Paletted */
		8,
		{ 0,0,0,0 } /* N/A */
	}, {
		/* B8G8R8A8 */
		24,
		{ 0x0000FF,
		  0x00FF00,
		  0xFF0000,
		  0x000000 }
	}, {
		/* A1B5G5R5 */
		16,
		{ 0x7C00,
		  0x03E0,
		  0x001F,
		  0x8000 },
	}
};

RageDisplay_GX::RageDisplay_GX() {
	VIDEO_Init();
	vmode = VIDEO_GetPreferredMode(NULL);
	
	// create frame buffers
	mXFB[0] = MEM_K0_TO_K1 (SYS_AllocateFramebuffer (vmode));
	mXFB[1] = MEM_K0_TO_K1 (SYS_AllocateFramebuffer (vmode));
	
	VIDEO_Configure(vmode);
	
	// clear buffers
	VIDEO_ClearFrameBuffer(vmode, mXFB[0], COLOR_BLACK);
	VIDEO_ClearFrameBuffer(vmode, mXFB[1], COLOR_BLACK);
	VIDEO_SetNextFramebuffer (mXFB[0]);
	
	// clear screen
	VIDEO_SetBlack(FALSE);
	VIDEO_Flush();
	VIDEO_WaitVSync();
	if(vmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
	
	// create FIFO memory
	mGPFIFO = memalign(32, DEFAULT_FIFO_SIZE);
	
	// erase memory
	memset(mGPFIFO, 0, DEFAULT_FIFO_SIZE);
	
	memset(mTextures, 0, sizeof(GXTexture*) * MAX_TEXTURES);
	
	mFB = 0;
	
	mZWrite = true;
	mZTest = true;
	mFirstFrame = true;
	mTexWrap = true;
	
	origOUT = devoptab_list[STD_OUT];
	origERR = devoptab_list[STD_ERR];
	
	EnableConsole(true);
}

RageDisplay_GX::~RageDisplay_GX() {}

void RageDisplay_GX::EnableConsole(bool enable) {
	if(enable) {
		CON_Init(mXFB[0], 20, 20, vmode->fbWidth, vmode->xfbHeight, vmode->fbWidth * VI_DISPLAY_PIX_SZ);
	} else {
		// restore devoptab so we stop using the console
		devoptab_list[STD_OUT] = origOUT;
		devoptab_list[STD_ERR] = origERR;
		// initialize GX memory
		GX_Init(mGPFIFO, DEFAULT_FIFO_SIZE);
		GXColor background = { 0, 0, 0, 0xff };
		VIDEO_Flush();
		VIDEO_WaitVSync();
		GX_SetCopyClear(background, 0x00ffffff);
		
		// set up initial viewport
		GX_SetViewport(0,0,vmode->fbWidth,vmode->efbHeight,0,1);
		f32 yscale = GX_GetYScaleFactor(vmode->efbHeight,vmode->xfbHeight);
		u32 xfbHeight = GX_SetDispCopyYScale(yscale);
		GX_SetScissor(0,0,vmode->fbWidth,vmode->efbHeight);
		GX_SetDispCopySrc(0,0,vmode->fbWidth,vmode->efbHeight);
		GX_SetDispCopyDst(vmode->fbWidth,xfbHeight);
		GX_SetCopyFilter(vmode->aa,vmode->sample_pattern,GX_TRUE,vmode->vfilter);
		GX_SetFieldMode(vmode->field_rendering,((vmode->viHeight==2*vmode->xfbHeight)?GX_ENABLE:GX_DISABLE));

		//GX_SetPixelFmt (GX_PF_RGB8_Z24, GX_ZC_LINEAR);
		GX_SetDispCopyGamma (GX_GM_1_0);
		GX_SetCullMode (GX_CULL_NONE);
		GX_SetBlendMode(GX_BM_BLEND,GX_BL_DSTALPHA,GX_BL_INVSRCALPHA,GX_LO_CLEAR);

		GX_SetZMode (GX_TRUE, GX_LEQUAL, GX_TRUE);
		GX_SetColorUpdate (GX_TRUE);
		GX_SetNumChans(1);
		GX_SetNumTexGens(1);
		
		// configure vertex format
		GX_ClearVtxDesc();
		GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
		GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
		GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
		
		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
		GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
		
		// configure texture format
		GX_SetTevOp(GX_TEVSTAGE0, GX_BLEND);
		GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
		GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY);
		
		GX_SetZMode(mZTest ? GX_TRUE : GX_FALSE, GX_ALWAYS, mZWrite ? GX_TRUE : GX_FALSE);
		
		GX_SetColorUpdate(GX_TRUE);
	}
}

void RageDisplay_GX::Update(float fDeltaTime) {
}


void RageDisplay_GX::ResolutionChanged() {
	SetViewport(0, 0);
	if(BeginFrame())
		EndFrame();
}

const RageDisplay::PixelFormatDesc *RageDisplay_GX::GetPixelFormatDesc(PixelFormat pf) const {
	ASSERT( pf < NUM_PIX_FORMATS );
	return &PIXEL_FORMAT_DESC[pf];
}


bool RageDisplay_GX::BeginFrame() {
	if(mBeginFrame)
		return false;
	
	mBeginFrame = true;
	
	SetViewport(0, 0);
	GX_SetNumTexGens(1);
	GX_InvalidateTexAll();
	GX_InvVtxCache();
	
	return true;
}

void RageDisplay_GX::EndFrame() {
	if(!mBeginFrame)
		BeginFrame();
	
	GX_CopyDisp(mXFB[mFB], GX_TRUE);
	
	GX_DrawDone();
	
	VIDEO_SetNextFramebuffer(mXFB[mFB]);
	
	mFB ^= 1;
	
	VIDEO_Flush();
	VIDEO_WaitVSync();
	
	mSwapBuffer = false;
	
	mBeginFrame = false;
}

RageDisplay::VideoModeParams RageDisplay_GX::GetVideoModeParams() const {
	VideoModeParams params;
	params.windowed = false;
	params.width = vmode->fbWidth;
	params.height = vmode->efbHeight;
	params.bpp = 32;
	params.PAL = VIDEO_GetCurrentTvMode() == VI_PAL;
	params.rate = params.PAL ? 50 : 60;
	params.vsync = true;
	params.bSmoothLines = false;
	params.bTrilinearFiltering = false;
	params.bAnisotropicFiltering = false;
	params.interlaced = vmode->viTVMode & VI_INTERLACE;
	return params;
}

void RageDisplay_GX::SetBlendMode( BlendMode mode ) {
	switch(mode) {
		case BLEND_NORMAL:
			GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
			break;
		case BLEND_ADD:
			GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR);
			break;
		case BLEND_NO_EFFECT:
			GX_SetBlendMode(GX_BM_BLEND, GX_BL_ZERO, GX_BL_ONE, GX_LO_CLEAR);
			break;
		default:
			ASSERT(0);
	}
}

bool RageDisplay_GX::SupportsTextureFormat( PixelFormat pixfmt, bool realtime ) {
	return pixfmt == RageDisplay::FMT_RGBA8;
}

unsigned RageDisplay_GX::FindFreeTextureSlot() {
	for(int i = 0; i < MAX_TEXTURES; i++) {
		if(mTextures[i] == NULL)
			return i;
	}
	return -1;
}

unsigned RageDisplay_GX::CreateTexture( PixelFormat pixfmt, RageSurface* img, bool bGenerateMipMaps ) {
	GXTexObj *obj = (GXTexObj*)malloc(sizeof(GXTexObj));
	u8 wrap = mTexWrap ? GX_REPEAT : GX_CLAMP;
	uint32_t *pixels = Metaphrasis::convertBufferToRGBA8((uint32_t*)img->pixels, img->w, img->h);
	GX_InitTexObj(obj, pixels, img->w, img->h, GX_TF_RGBA8, wrap, wrap, GX_FALSE);
	GXTexture *tex = new GXTexture();
	tex->index = FindFreeTextureSlot();
	tex->texture = obj;
	tex->xoffset = 0;
	tex->yoffset = 0;
	tex->pixels = pixels;
	mTextures[tex->index] = tex;
	return tex->index;
}

void RageDisplay_GX::UpdateTexture( unsigned uTexHandle, RageSurface* img, int xoffset, int yoffset, int width, int height ) {
	if(uTexHandle >= MAX_TEXTURES || uTexHandle < 0)
		return;
	if(mTextures[uTexHandle] == NULL)
		return;
	
	GXTexture *tex = mTextures[uTexHandle];
	tex->xoffset = xoffset;
	tex->yoffset = yoffset;
	GXTexObj *obj = tex->texture;
	u8 wrap = mTexWrap ? GX_REPEAT : GX_CLAMP;
	uint32_t *pixels = Metaphrasis::convertBufferToRGBA8((uint32_t*)img->pixels, img->w, img->h);
	free(tex->pixels);
	tex->pixels = pixels;
	GX_InitTexObj(obj, pixels, width, height, GX_TF_RGBA8, wrap, wrap, GX_FALSE);
}

void RageDisplay_GX::DeleteTexture( unsigned uTexHandle ) {
	if(uTexHandle >= MAX_TEXTURES || uTexHandle < 0)
		return;
	if(mTextures[uTexHandle] == NULL)
		return;
	
	GXTexture *tex = mTextures[uTexHandle];
	free(tex->pixels);
	free(tex->texture);
	free(tex);
	mTextures[uTexHandle] = NULL;
}

void RageDisplay_GX::ClearAllTextures() {
	GX_InvalidateTexAll();
}

void RageDisplay_GX::SetTexture( int iTextureUnitIndex, RageTexture* pTexture ) {
	if(pTexture == NULL) {
		return;
	}
	if(mTextures[pTexture->GetTexHandle()] == NULL) {
		LOG->Warn("SetTexture: Unable to find texture in texture map");
		return;
	}
	
	GXTexObj *obj = mTextures[pTexture->GetTexHandle()]->texture;
	switch(iTextureUnitIndex) {
		case 0:
			GX_LoadTexObj(obj, GX_TEXMAP0);
			break;
		case 1:
			GX_LoadTexObj(obj, GX_TEXMAP0);
			break;
		default:
			ASSERT(0);
	}
}

void RageDisplay_GX::SetTextureModeModulate() {
	GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
}

void RageDisplay_GX::SetTextureModeGlow( GlowMode m ) {
	GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR);
}

void RageDisplay_GX::SetTextureModeAdd() {
	GX_SetTevOp(GX_TEVSTAGE0, GX_BLEND);
}

void RageDisplay_GX::SetTextureWrapping( bool b ) {
	mTexWrap = b;
}

int RageDisplay_GX::GetMaxTextureSize() const {
	return 1024;
}

void RageDisplay_GX::SetTextureFiltering( bool b) {
}

bool RageDisplay_GX::IsZWriteEnabled() const {
	return mZWrite;
}

bool RageDisplay_GX::IsZTestEnabled() const {
	return mZTest;
}

void RageDisplay_GX::SetZWrite( bool b ) {
	mZWrite = b;
	GX_SetZMode(mZTest ? GX_TRUE : GX_FALSE, GX_ALWAYS, mZWrite ? GX_TRUE : GX_FALSE);
}

void RageDisplay_GX::SetZTestMode( ZTestMode mode ) {
	mZTest = mode != ZTEST_OFF;
	GX_SetZMode(mZTest ? GX_TRUE : GX_FALSE, GX_ALWAYS, mZWrite ? GX_TRUE : GX_FALSE);
}

void RageDisplay_GX::ClearZBuffer() {
	GX_SetCopyClear((GXColor) { 0, 0, 0, 0xff }, GX_MAX_Z24);
}

void RageDisplay_GX::SetCullMode( CullMode mode ) {
	switch(mode) {
		case CULL_BACK:
			GX_SetCullMode(GX_CULL_BACK);
			break;
		case CULL_FRONT:
			GX_SetCullMode(GX_CULL_FRONT);
			break;
		case CULL_NONE:
			GX_SetCullMode(GX_CULL_NONE);
			break;
		default:
			ASSERT(0);
	}
}

void RageDisplay_GX::SetAlphaTest( bool b ) {
	GX_PokeAlphaMode(b ? GX_ALWAYS : GX_NEVER, 2);
}

void RageDisplay_GX::SetMaterial( const RageColor &emissive, const RageColor &ambient, const RageColor &diffuse, const RageColor &specular, float shininess) {
	GX_SetChanAmbColor(GX_COLOR0A0, (GXColor){ (int)(ambient.r * 255), (int)(ambient.g * 255), (int)(ambient.b * 255), (int)(ambient.a * 255) });
}

void RageDisplay_GX::SetLighting( bool b ) {
}

void RageDisplay_GX::SetLightOff( int index ) {
	GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
	
	GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, 0, GX_DF_NONE, GX_AF_NONE);
	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
}

void RageDisplay_GX::SetLightDirectional( int index, const RageColor &ambient, const RageColor &diffuse, const RageColor &specular, const RageVector3 &dir ) {
	
}


void RageDisplay_GX::SetSphereEnironmentMapping( bool b ) {
}


RageCompiledGeometry* RageDisplay_GX::CreateCompiledGeometry() {
	return NULL;
}

void RageDisplay_GX::DeleteCompiledGeometry( RageCompiledGeometry* p ) {
}


void RageDisplay_GX::DrawQuadsInternal( const RageSpriteVertex v[], int iNumVerts ) {
	SendCurrentMatrices();
	
	GX_Begin(GX_QUADS, GX_VTXFMT0, iNumVerts);
	for(int i = 0; i < iNumVerts; i++) {
		GX_Position3f32(v[i].p.x, v[i].p.y, v[i].p.z);
		GX_Color4u8(    v[i].c.r, v[i].c.g, v[i].c.b, v[i].c.a);
		GX_TexCoord2f32(v[i].t.x, v[i].t.y);
		//GX_Normal3f32(  v[i].n.x, v[i].n.y, v[i].n.z);
	}
	GX_End();
}

void RageDisplay_GX::DrawQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) {
	LOG->Info("DrawQuadStrip");
	SendCurrentMatrices();
	
	bool rewind = false;
	int numVerts = ((iNumVerts - 4) * 2) + 4;
	
	GX_Begin(GX_QUADS, GX_VTXFMT0, numVerts);
	for(int i = 0; i < numVerts; i++) {
		if(i > 3 && ((i & 1) == 0)) {
			if(!rewind) {
				i -= 2;
				rewind = true;
			} else {
				rewind = false;
			}
		}
		GX_Position3f32(v[i].p.x, v[i].p.y, v[i].p.z);
		GX_Color4u8(v[i].c.r, v[i].c.g, v[i].c.b, v[i].c.a);
		GX_TexCoord2f32(v[i].t.x, v[i].t.y);
		//GX_Normal3f32(v[i].n.x, v[i].n.y, v[i].n.z);
	}
	GX_End();
	
}

void RageDisplay_GX::DrawFanInternal( const RageSpriteVertex v[], int iNumVerts ) {
	SendCurrentMatrices();
	
	GX_Begin(GX_TRIANGLEFAN, GX_VTXFMT0, iNumVerts);
	for(int i = 0; i < iNumVerts; i++) {
		GX_Position3f32(v[i].p.x, v[i].p.y, v[i].p.z);
		GX_Color4u8(v[i].c.r, v[i].c.g, v[i].c.b, v[i].c.a);
		GX_TexCoord2f32(v[i].t.x, v[i].t.y);
		//GX_Normal3f32(v[i].n.x, v[i].n.y, v[i].n.z);
	}
	GX_End();
}

void RageDisplay_GX::DrawStripInternal( const RageSpriteVertex v[], int iNumVerts ) {
	LOG->Info("DrawStrip");
	SendCurrentMatrices();
	
	GX_Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, iNumVerts);
	for(int i = 0; i < iNumVerts; i++) {
		GX_Position3f32(v[i].p.x, v[i].p.y, v[i].p.z);
		GX_Color4u8(v[i].c.r, v[i].c.g, v[i].c.b, v[i].c.a);
		GX_TexCoord2f32(v[i].t.x, v[i].t.y);
		//GX_Normal3f32(v[i].n.x, v[i].n.y, v[i].n.z);
	}
	GX_End();
}

void RageDisplay_GX::DrawTrianglesInternal( const RageSpriteVertex v[], int iNumVerts ) {
	LOG->Info("DrawTriangles");
	SendCurrentMatrices();
	
	GX_Begin(GX_TRIANGLES, GX_VTXFMT0, iNumVerts);
	for(int i = 0; i < iNumVerts; i++) {
		GX_Position3f32(v[i].p.x, v[i].p.y, v[i].p.z);
		GX_Color4u8(v[i].c.r, v[i].c.g, v[i].c.b, v[i].c.a);
		GX_TexCoord2f32(v[i].t.x, v[i].t.y);
		//GX_Normal3f32(v[i].n.x, v[i].n.y, v[i].n.z);
	}
	GX_End();
}

void RageDisplay_GX::DrawCompiledGeometryInternal( const RageCompiledGeometry *p, int iMeshIndex ) {
	LOG->Info("DrawCompiledGeometry(%p, %d)", p, iMeshIndex);
}


CString RageDisplay_GX::TryVideoMode( RageDisplay::VideoModeParams params, bool &bNewDeviceOut ) {
	LOG->Info("TryVideoMode");
	SetDefaultRenderStates();
	return "";
}

RageSurface* RageDisplay_GX::CreateScreenshot() {
	return NULL;
}

void RageDisplay_GX::SetViewport(int shift_left, int shift_down) {
	shift_left = int( shift_left * float(vmode->fbWidth) / SCREEN_WIDTH );
	shift_down = int( shift_down * float(vmode->efbHeight) / SCREEN_HEIGHT );

	GX_SetViewport(shift_left, shift_down, vmode->fbWidth, vmode->efbHeight, 0, 1);
}

RageMatrix RageDisplay_GX::GetOrthoMatrix( float l, float r, float b, float t, float zn, float zf ) {
	RageMatrix ortho;
	guOrtho(ortho.m, t, b, l, r, zn, zf);
	ortho.perspective = false;
	
	return ortho;
}

RageMatrix RageDisplay_GX::GetFrustumMatrix(float l, float r, float b, float t, float zn, float zf) {
	RageMatrix frust;
	guFrustum(frust.m, t, b, l, r, zn, zf);
	frust.perspective = false;
	
	return frust;
}


void RageDisplay_GX::SendCurrentMatrices() {
	// Projection
	//const RageMatrix* top = GetProjectionTop();
	//GX_LoadProjectionMtx((Mtx44P)top->m, top->perspective ? GX_PERSPECTIVE : GX_ORTHOGRAPHIC);
	RageMatrix top = GetOrthoMatrix(0, vmode->fbWidth, vmode->xfbHeight, 0, -1000, 1000);
	GX_LoadProjectionMtx((Mtx44P)top.m, GX_ORTHOGRAPHIC);
	
	// Modelview
	RageMatrix _model = RageMatrixIdentity();
	RageMatrixMultiply(&_model, GetCentering(), GetViewTop());
	RageMatrixMultiply(&_model, &_model, GetWorldTop());
	RageMatrix model;
	RageMatrixTranspose(&model, &_model);
	
	Mtx modelview;
	modelview[0][0] = model.m[0][0]; modelview[0][1] = model.m[0][1]; modelview[0][2] = model.m[0][2]; modelview[0][3] = model.m[0][3];
	modelview[1][0] = model.m[1][0]; modelview[1][1] = model.m[1][1]; modelview[1][2] = model.m[1][2]; modelview[1][3] = model.m[1][3];
	modelview[2][0] = model.m[2][0]; modelview[2][1] = model.m[2][1]; modelview[2][2] = model.m[2][2]; modelview[2][3] = model.m[2][3];
	
	GX_LoadPosMtxImm(modelview, GX_PNMTX0);
	
	// Texture
	GX_LoadTexMtxImm((Mtx44P)GetTextureTop()->m, GX_TEXMTX0, GX_MTX3x4);
	
	GX_SetCurrentMtx(GX_PNMTX0);
}

